package cn.itcast.nems.product.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.itcast.nems.common.PageQueryModel;
import cn.itcast.nems.common.bean.ProductState;
import cn.itcast.nems.manager.util.CustomBeanUtil;
import cn.itcast.nems.product.api.ProductApi;
import cn.itcast.nems.product.dao.dataobject.BizNo;
import cn.itcast.nems.product.dao.dataobject.ProductDO;
import cn.itcast.nems.product.dao.dataobject.ProductPriceDO;
import cn.itcast.nems.product.dao.entity.Product;
import cn.itcast.nems.product.dao.entity.ProductLog;
import cn.itcast.nems.product.dao.entity.ProductPriceRule;
import cn.itcast.nems.product.dao.mapper.ProductLogMapper;
import cn.itcast.nems.product.dao.mapper.ProductMapper;
import cn.itcast.nems.product.dto.*;
import cn.itcast.nems.product.service.ProductPriceRuleService;
import cn.itcast.nems.product.service.ProductService;
import cn.itcast.nems.product.service.ProductSnapshotService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 产品 服务实现类
 * </p>
 *
 * @author liyong
 * @since 2023-08-31
 */
@Slf4j
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService, ProductApi {

    private final ObjectMapper objectMapper;
    private final ProductMapper productMapper;
    private final ProductLogMapper productLogMapper;
    private final ProductSnapshotService productSnapshotService;
    private final ProductPriceRuleService productPriceRuleService;

    public ProductServiceImpl(ProductMapper productMapper,
                              ProductLogMapper productLogMapper,
                              ProductSnapshotService productSnapshotService,
                              ProductPriceRuleService productPriceRuleService) {
        this.productMapper = productMapper;
        this.productLogMapper = productLogMapper;
        this.productSnapshotService = productSnapshotService;
        this.productPriceRuleService = productPriceRuleService;
        this.objectMapper = new ObjectMapper();
    }

    @Override
    public ProductDO findProduct(String bizNo) {
        return this.productMapper.selectProduct(bizNo);
    }

    @Override
    public List<ProductDO> findListByOrderId(String orderId) {
        return productMapper.findListByOrderId(orderId);
    }

    @Override
    public List<ProductDO> findProducts(int clazzId) {
        final String bizNo = BizNo.calBizNo(clazzId);
        final ProductDO productDO = this.productMapper.selectProduct(bizNo);
        if (productDO != null) {
            return List.of(productDO);
        }
        return this.productMapper.selectProductsByLikeBizNo(bizNo);
    }

    @Override
    public ProductDTO getProduct(long productId) {
        final Product product = this.productMapper.selectById(productId);
        ProductDTO result = new ProductDTO();
        BeanUtils.copyProperties(product, result);
        final Map<Long, String> snapshotIds = this.productSnapshotService.findSnapshotIds(Set.of(productId));
        result.setProductSnapshotId(snapshotIds.get(productId));
        final ProductPriceRule rule = this.productPriceRuleService.getCurrentPriceRuleByProduct(productId);
        if(rule != null){
            result.setPrice(rule.getPrice());
            result.setPostpaidPrice(rule.getPostpaidPrice());
            result.setListPrice(rule.getListPrice());
        }
        return result;
    }

    @Override
    public cn.itcast.ic.common.bean.Page<ProductClazzDTO> findClazzPageList(PageQueryModel<ProductClazzQueryDTO> queryModel) {
        Page<ProductPriceDO> page = new Page<>();
        page.setCurrent(queryModel.getPageNumber());
        page.setSize(queryModel.getPageSize());
        ProductClazzQueryDTO queryDTO = Objects.nonNull(queryModel.getCondition()) ? queryModel.getCondition(): new ProductClazzQueryDTO() ;
        ProductPriceDO priceDO =  CustomBeanUtil.copyProperties(queryDTO , new ProductPriceDO()) ;
        IPage<ProductPriceDO> data = productMapper.findClazzPageList(page, priceDO);
        List<ProductClazzDTO> list = CollectionUtil.isEmpty(data.getRecords()) ? null :
                data.getRecords().stream().map(item -> {
                    ProductClazzDTO clazzDTO = CustomBeanUtil.copyProperties(item, new ProductClazzDTO()) ;
                    clazzDTO.setPrice(BigDecimal.valueOf(item.getPrice()).divide(BigDecimal.valueOf(100),2,RoundingMode.HALF_UP).toString());
                    clazzDTO.setPostpaidPrice(BigDecimal.valueOf(item.getPostpaidPrice()).divide(BigDecimal.valueOf(100),2,RoundingMode.HALF_UP).toString());
                    return clazzDTO ;
                }).toList();
        return cn.itcast.ic.common.bean.Page.of(queryModel.getPageNumber(), queryModel.getPageSize(), Long.valueOf(data.getTotal()).intValue(), list);
    }

    @Override
    public int flushInvalidState() {
        LocalDate limitDate = LocalDate.now();
        final int count = this.productMapper.updateInvalidState(limitDate);
        log.info("更新了{}个商品状态为下架", count);
        return count;
    }

    @Override
    public IPage<ProductPriceDO> findPageList(Page<ProductPriceDO> page, ProductPriceDO priceDO) {
        return productMapper.findPageList(page, priceDO);
    }

    @Override
    public List<ProductDO> findProducts(LocalDate startDate,
                                        LocalDate endDate,
                                        ProductState state,
                                        Boolean includeExpired) {
        return this.productMapper.selectProducts(startDate, endDate, state, includeExpired);
    }

    @Override
    public List<ProductDO> findProducts(String classTypeId,
                                        Set<String> campusIds) {
        return this.productMapper.selectProductsByClassTypeId(classTypeId, campusIds);
    }

    @Override
    public List<ProductDO> findProducts(List<ClassTypeCampusDTO> classTypeCampus) {
        List<ProductDO> result = new ArrayList<>();
        if (classTypeCampus != null) {
            classTypeCampus.forEach(ctc -> {
                final List<ProductDO> dos = this.productMapper.selectProductsByClassTypeId(ctc.getClassTypeId(), ctc.getCampusIds());
                result.addAll(dos);
            });
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateProductStates(List<ProductDO> productDOs, String operator) {
        if (productDOs == null || productDOs.isEmpty()) {
            return 0;
        }
        final Set<Long> productIds = productDOs.stream().map(ProductDO::getId).collect(Collectors.toSet());
        final List<Product> products = this.productMapper.selectBatchIds(productIds);
        final Map<Long, String> snapshotIdMap = this.productSnapshotService.findSnapshotIds(productIds);
        final Map<Long, ProductDO> productDOMap = productDOs
                .stream()
                .collect(Collectors.toMap(p -> p.getId(), p -> p));
        int success = 0;
        for (Product p : products) {
            final ProductDO productDO = productDOMap.get(p.getId());
            final String snapshotId = snapshotIdMap.get(p.getId());
            boolean b = this.updateProductState(p, productDO, snapshotId, operator);
            if (b) {
                success++;
            }
        }
        return success;
    }

    private boolean updateProductState(Product product, ProductDO productDO, String preProductSnapshotId, String operator) {
        try {
            Product updateProduct = new Product();
            updateProduct.setId(product.getId());
            updateProduct.setState(productDO.getState());
            this.productMapper.updateById(updateProduct);

            final String snapshotId = this.addProductSnapshot(product, preProductSnapshotId, operator);

            this.addProductLog(product, snapshotId, operator);
            return true;
        } catch (Exception e) {
            log.error(String.format("更新产品状态失败！产品编号：%s", product.getBizNo()), e);
            return false;
        }
    }

    private String addProductSnapshot(Product product, String preProductSnapshotId, String operator) throws JsonProcessingException {
        ProductSnapshotDTO snapshot = new ProductSnapshotDTO();
        snapshot.setProductId(product.getId());
        snapshot.setPreviousSnapshotId(preProductSnapshotId);
        final String productJson = this.objectMapper.writeValueAsString(product);
        snapshot.setSnapshotContent(productJson);
        return this.productSnapshotService.addProductSnapshot(snapshot, operator);
    }

    private void addProductLog(Product product, String snapshotId, String operator) throws JsonProcessingException {
        ProductLog log = new ProductLog();
        log.setProductId(product.getId());
        log.setSnapshotId(snapshotId);
        log.setType("价格修改");
        log.setPriceChangeFlag(true);
        log.setDetail("修改产品价格");
        log.setCreatedBy(operator);
        log.setCreatedDatetime(LocalDateTime.now());
        this.productLogMapper.insert(log);
    }

    @Override
    public int increaseInventorySoldQuantity(Long productId, Integer quantity) {
        log.debug("增加产品库存数量，产品ID：{}，减少数量：{}", productId, quantity);
        // TODO 需要考虑库存精确控制的完整逻辑
        int ct = productMapper.increaseInventorySoldQuantity(productId, quantity);
        return ct;
    }

    @Override
    public int decreaseInventorySoldQuantity(Long productId, Integer quantity) {
        log.debug("减少产品库存数量，产品ID：{}，减少数量：{}", productId, quantity);
        // TODO 需要考虑库存精确控制的完整逻辑
        int ct = productMapper.decreaseInventorySoldQuantity(productId, quantity);
        return ct;
    }

    @Override
    public int increaseInventoryQuantity(Long productId, Integer quantity) {
        // TODO 需要考虑库存精确控制的完整逻辑
        int ct = productMapper.increaseInventoryQuantity(productId, quantity);
        return ct;
    }

    @Override
    public ProductDTO findProductByIdOrBizNo(Long id, String bizNo) {
        Product product = this.getOne(new LambdaQueryWrapper<Product>()
                                        .eq(Objects.nonNull(id), Product::getId , id)
                                        .eq(StringUtils.hasText(bizNo), Product::getBizNo, bizNo)
                                        .last(" order by created_datetime desc limit 1 "));
        if(Objects.nonNull(product)) {
            ProductDTO rst = new ProductDTO();
            BeanUtils.copyProperties(product, rst);
            return rst;
        }
        return null;
    }
}
